/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Graphics
{
	public struct GlyphLayout
	{
		public Texture2D Texture;

		public Vector2 Origin;
		public Vector2 Size;

		public Vector2 Scale;

		public Rectangle SourceRectangle;

		public Color Color;
	}

	public sealed class FormattingStack
	{
		private struct FormatState
		{
			public Color Color;
		}

		public FormattingStack()
		{
			defaultFormat.Color = Color.White;
		}

		private FormatState defaultFormat;
		private Stack<FormatState> stack = new Stack<FormatState>( 16 );

		public Color DefaultColor
		{
			get { return defaultFormat.Color; }
			set { defaultFormat.Color = value; }
		}

		private FormatState Top()
		{
			if( stack.Count > 0 )
				return stack.Peek();
			return defaultFormat;
		}

			internal void Clear()
			{
				stack.Clear();
			}

		public void Pop()
		{
			if( stack.Count > 0 )
				stack.Pop();
		}

		public Color Color
		{
			get { return Top().Color; }
		}

		public void PushColor( Color newColor )
		{
			FormatState state = Top();
			state.Color = newColor;
			stack.Push( state );
		}
	}

	public enum GlyphScaleOrigin
	{
		TopLeft,
		Center,
	}

	public enum GlyphDrawFlags
	{
		None,
		IgnoreColors,
	}

	public sealed class GlyphLayoutEngine
	{
		public GlyphLayoutEngine()
		{
		}

		private FormattingStack format = new FormattingStack(); 

		private int lineIdx;
		private LineLayoutEngine lines;
		private long textPos;

		private Font.FontPoint basePoint;
		private Vector2 baseScale;
		private float letterScale;

		private float xPos, prevKernC;
		private Vector2 offset;

		public Color DefaultColor
		{
			get { return format.DefaultColor; }
			set { format.DefaultColor = value; }
		}

		private GlyphDrawFlags drawFlags;
		public GlyphDrawFlags DrawFlags
		{
			get { return drawFlags; }
			set { drawFlags = value; }
		}

		private bool formatChanged;
		/// <summary>
		/// True when the last layed out character has a different format (color) than the last.
		/// </summary>
		public bool FormatChanged { get { return formatChanged; } }

		private GlyphScaleOrigin scaleOrigin;

		public void Reset()
		{
			lines = null;

			formatChanged = false;
		}

		public void Initialize( LineLayoutEngine lines,
			Vector2 offset, float letterScale,
			GlyphScaleOrigin scaleOrigin )
		{
			if( lines == null )
				throw new ArgumentNullException( "lines" );

			this.lines = lines;
			this.lineIdx = -1;
			this.textPos = -1;

			this.offset = offset;
			this.letterScale = letterScale;
			this.scaleOrigin = scaleOrigin;

			this.format.Clear();

			this.formatChanged = false;
		
			this.basePoint = lines.font.GetPoint( lines.TextHelper.GetPointSize( lines.textScale ) );
			this.baseScale = lines.TextHelper.GetGlyphScale( lines.textScale, basePoint.PointSize );
		}

		public void Initialize( LineLayoutEngine lines, float letterScale )
		{
			Initialize( lines, Vector2.Zero, letterScale, GlyphScaleOrigin.Center );
		}

		public void Initialize( LineLayoutEngine lines )
		{
			Initialize( lines, Vector2.Zero, 1, GlyphScaleOrigin.TopLeft );
		}

		private unsafe bool LayoutNextGlyph( char *srcText, out GlyphLayout glyph )
		{
		begin:
			bool lineStart;
			LineLayout line;
			
			if( textPos < 0 )
			{
				//either the first call, or the last

				if( ++lineIdx >= lines.Count )
				{
					Reset();

					glyph = default( GlyphLayout );
					return false;
				}

				line = lines.GetLine( lineIdx );
				textPos = line.textOffset;

				xPos = 0;
				prevKernC = 0;

				lineStart = true;

				if( lineIdx == 0 )
					formatChanged = true;
			}
			else
			{
				line = lines.GetLine( lineIdx );
				lineStart = false;
			}

			char* text = srcText + textPos;
			char* textEnd = srcText + line.textOffset + line.charCount;

			while( text < textEnd )
			{
				int formatCharCount = lines.TextHelper.ProcessFormattingSequence(
					text, (int)(textEnd - text), format );

				if( formatCharCount == 0 )
					break;

				formatChanged = true;

				text += formatCharCount;
				textPos += formatCharCount;
			}

			if( text >= textEnd )
			{
				textPos = -1; //go to next line
				goto begin;
			}

			int charSize;
			int ch = TextHelper.DecodeCharacter( text, out charSize );
			textPos += charSize;
			text += charSize;

			if( ch == '\n' )
				goto begin;

			int gIdx = lines.font.GetCharacterIndex( ch );
			if( gIdx < 0 )
				//skip missing glyphs
				goto begin;

			if( !lineStart )
				xPos += prevKernC;

			float x = xPos;

			xPos += (basePoint.Glyphs[gIdx].KernA + basePoint.Glyphs[gIdx].KernB) * baseScale.X;
			prevKernC = basePoint.Glyphs[gIdx].KernC * baseScale.X;

#if XBOX
			glyph = default( GlyphLayout );
#endif

			glyph.Texture = basePoint.Glyphs[gIdx].Sheet;
			
			glyph.Origin.X = offset.X + line.origin.X + x +
				basePoint.Glyphs[gIdx].DrawOffset.X * baseScale.X;
			glyph.Origin.Y = offset.Y + line.origin.Y +
				basePoint.Glyphs[gIdx].DrawOffset.Y * baseScale.Y;

			glyph.SourceRectangle = basePoint.Glyphs[gIdx].SourceRectangle;

			glyph.Scale.X = baseScale.X * letterScale;
			glyph.Scale.Y = baseScale.Y * letterScale;

			switch( scaleOrigin )
			{
			case GlyphScaleOrigin.TopLeft:
				break;

			case GlyphScaleOrigin.Center:
				glyph.Origin.X += glyph.SourceRectangle.Width * baseScale.X * ( 1 - letterScale) * 0.5F;
				glyph.Origin.Y += glyph.SourceRectangle.Height * baseScale.Y * (1 - letterScale) * 0.5F;
				break;

			default:
				goto case GlyphScaleOrigin.TopLeft;
			}

			glyph.Size.X = basePoint.Glyphs[gIdx].DrawSize.X * glyph.Scale.X;
			glyph.Size.Y = basePoint.Glyphs[gIdx].DrawSize.Y * glyph.Scale.Y;

			glyph.Color = (drawFlags & GlyphDrawFlags.IgnoreColors) != GlyphDrawFlags.None ? format.DefaultColor : format.Color;

			return true;
		}

		public unsafe bool LayoutNextGlyph( out GlyphLayout glyph )
		{
			if( lines == null )
				throw new InvalidOperationException();

			if( lines.sourceString != null )
			{
				fixed( char* text = lines.sourceString )
					return LayoutNextGlyph( text + lines.sourceOffset, out glyph );
			}
			else if( lines.sourceChars != null )
			{
				fixed( char* text = lines.sourceChars )
					return LayoutNextGlyph( text + lines.sourceOffset, out glyph );				
			}
			else
				throw new InvalidOperationException();
		}

		public void Draw( SpriteBatch spriteBatch )
		{
			GlyphLayout glyph;
			while( LayoutNextGlyph( out glyph ) )
			{
				Vector2 origin = glyph.Origin;
				//origin.X = (int)(glyph.Origin.X + 0.5F);
				//origin.Y = (int)(glyph.Origin.Y + 0.5F);

				spriteBatch.Draw( glyph.Texture, origin, glyph.SourceRectangle,
					glyph.Color, 0, Vector2.Zero, glyph.Scale, SpriteEffects.None, 0 );
			}
		}

	}
}